package sweetsaremeat.beehive.Model;



import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import sweetsaremeat.beehive.Model.Node.Side;

public class Map 
{
	/** Enumerators */
	//the types of content
	public static enum Content 
	{
	    Zero,
	    One
	}
	
	public boolean[][] m_contents;
	
	public Node m_startNode;
	
	public Random m_random;
	
	public PathGenerator m_pathGenerator;
	
	public Map(long a_randomSeed)
	{
		m_random = new Random(a_randomSeed);
		
		//GenerateContentMatrix();
		
		GenerateBaseMap();
		
		m_pathGenerator = new PathGenerator(this);
		m_pathGenerator.GenerateBasePath();
	}
	
	public void GenerateBaseMap()
	{
		System.out.println("generating map..");
		
		//create the start node
		m_startNode = new Node(0, 0);
		m_startNode.m_visible = true;
		
		
		//first we create the visible nodes
		
		//this creates on all sides of the start node
		for(Node.Side s : Node.Side.values())
		{
			m_startNode.CreateNeighbor(s);
			m_startNode.m_neighbors[s.ordinal()].m_visible = true;
		}
		
		//increase the radius with nodes!
		for(Node.Side s : Node.Side.values())
		{
			for(Node.Side n : Node.Side.values())
			{
				if(m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()] == null)
				{
					m_startNode.m_neighbors[s.ordinal()].CreateNeighbor(n);
					m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].m_visible = true;
				}
			}
		}
		
		
		//now we create the invisible nodes (the ones in the fog of war)
		
		//increase the radius with nodes!
		for(Node.Side s : Node.Side.values())
		{
			for(Node.Side n : Node.Side.values())
			{
				for(Node.Side i : Node.Side.values())
				{
					if(m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].m_neighbors[i.ordinal()] == null)
					{
						m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].CreateNeighbor(i);
					}
				}
			}
		}
		
		//increase the radius with more nodes!
		for(Node.Side s : Node.Side.values())
		{
			for(Node.Side n : Node.Side.values())
			{
				for(Node.Side i : Node.Side.values())
				{
					for(Node.Side j : Node.Side.values())
					{
						if(m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].m_neighbors[i.ordinal()].m_neighbors[j.ordinal()] == null)
						{
							m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].m_neighbors[i.ordinal()].CreateNeighbor(j);
						}
					}
				}
			}
		}
		/*
		//increase the radius with even more nodes!
		for(Node.Side s : Node.Side.values())
		{
			for(Node.Side n : Node.Side.values())
			{
				for(Node.Side i : Node.Side.values())
				{
					for(Node.Side j : Node.Side.values())
					{
						for(Node.Side k : Node.Side.values())
						{
							if(m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].m_neighbors[i.ordinal()].m_neighbors[j.ordinal()].m_neighbors[k.ordinal()] == null)
							{
								m_startNode.m_neighbors[s.ordinal()].m_neighbors[n.ordinal()].m_neighbors[i.ordinal()].m_neighbors[j.ordinal()].CreateNeighbor(k);
							}
						}
					}
				}
			}
		}
		*/
	}
	
	private void GenerateContentMatrix()
	{
		//create some variables (for optimization)
		Content[] f_contents = Content.values();
		int f_contentLength = f_contents.length, x = 0, y = 0;
		
		//initiate content table
		m_contents = new boolean[f_contentLength][f_contentLength];
		for(x = 0; x < f_contentLength; x++)
		{
			for(y = 0; y < f_contentLength; y++)
			{
				//if it's the same
				if(x == y)
				{
					m_contents[x][y] = true;
				}
				else
				{
					m_contents[x][y] = false;
				}
			}
		}
		
		
		//now create the connection of the contents
		for(Content c : f_contents)
		{
			switch(c)
			{
				//TODO do connections
			default:
				System.out.println("Map.GenerateContentTable WARNING: The content " + c + " has no connections!");
				break;
			}
		}
	}
	
	private void ConnectContent(Content a, Content b)
	{
		m_contents[a.ordinal()][b.ordinal()] = true;
		m_contents[b.ordinal()][a.ordinal()] = true;
	}
	
	
	public Node[] FindEdgeNodes(Vector2[] a_targets, Node a_startNode, int a_outwardDir)
	{
		//initialize some variables
		int i;
		Node[] f_foundNodes = new Node[a_targets.length];
		int f_foundCount = 0;
		Node f_leftNode;
		Node f_rightNode;
		int f_leftEdgeDir = 0, f_rightEdgeDir = 0;
		float f_posDiff;
		final float f_allowedDiff = 0.001f;
		
		boolean f_foundEdge = false;
		boolean f_foundNeighbor = false;
		//check if the input was bad
		for(i=0; i < Node.Side.values().length; i++)
		{
			if(a_startNode.m_neighbors[i] == null)
			{
				f_foundEdge = true;
			}
			else
			{
				f_foundNeighbor = true;
			}
		}
		if(!f_foundEdge)
		{
			throw new RuntimeException("FindEdgeNodes ERROR: The start node is not an edge node!");
		}
		if(!f_foundNeighbor)
		{
			throw new RuntimeException("FindEdgeNodes ERROR: The start node doesn't have any neighbors!");
		}
		
		//set the left edge node and that node's left edge direction
		
		//calculate the left edge direction of the start node
		f_leftEdgeDir = GetEdgeDirection(a_startNode, a_outwardDir, false);
		//set the left edge node
		f_leftNode = a_startNode.m_neighbors[Node.GetLeftSide(f_leftEdgeDir)];
		//calculate the new left edge direction of the left edge node
		f_leftEdgeDir = GetEdgeDirection(f_leftNode, Node.GetRightSide(f_leftEdgeDir), false);
		
		
		
		//set the right edge node and that node's right edge direction
		
		//calculate the right edge direction of the start node
		f_rightEdgeDir = GetEdgeDirection(a_startNode, a_outwardDir, true);
		//set the right edge node
		f_rightNode = a_startNode.m_neighbors[Node.GetRightSide(f_rightEdgeDir)];
		//calculate the new right edge direction of the right edge node
		f_rightEdgeDir = GetEdgeDirection(f_rightNode, Node.GetLeftSide(f_rightEdgeDir), true);
		
		
		
		//now we can start searching!
		
		//loops until we've made a full circle OR found all targets
		while(true)
		{
			//search left
			
			//check for targets
			//loop through all targets
			for(i=0; i < a_targets.length; i++)
			{
				//check if it already has been found
				if(a_targets[i] == null)
				{
					continue;
				}
				
				//calculate the difference of the x positions
				f_posDiff = f_leftNode.m_pos.x - a_targets[i].x;
				
				//check if it's the correct x position
				if(f_posDiff > -f_allowedDiff && f_posDiff < f_allowedDiff)
				{
					//calculate the difference of the y positions
					f_posDiff = f_leftNode.m_pos.y - a_targets[i].y;
					
					//check if it's the correct y position
					if(f_posDiff > -f_allowedDiff && f_posDiff < f_allowedDiff)
					{
						//we found it!
						f_foundNodes[i] = f_leftNode;
						
						f_foundCount++; //increase the counter
						
						//remove the target (to not search for it)
						a_targets[i] = null;
					}
				}
			}
			
			//move the left node along the edge
			f_leftNode = f_leftNode.m_neighbors[Node.GetLeftSide(f_leftEdgeDir)];
			
			//move the left edge direction (since we moved the left node)
			//NOTE: move it the opposite direction to get the correct direction from the new node's perspective
			f_leftEdgeDir = Node.GetRightSide(f_leftEdgeDir);
			
			//loop until we reached the next edge direction
			while(true)
			{
				//check if the next node is not null
				if(f_leftNode.m_neighbors[Node.GetLeftSide(f_leftEdgeDir)] != null)
				{
					break;
				}
				
				//move the left edge direction
				f_leftEdgeDir = Node.GetLeftSide(f_leftEdgeDir);
			}
			
			
			//before we search right..
			
			//check if we've gone in a full circle OR found all targets
			if(f_leftNode == f_rightNode || f_foundCount == a_targets.length)
			{
				break; //we've checked all edge nodes
			}
			
			
			//search right
			
			//check for targets
			//loop through all targets
			for(i=0; i < a_targets.length; i++)
			{
				//check if it already has been found
				if(a_targets[i] == null)
				{
					continue;
				}
				
				//calculate the difference of the x positions
				f_posDiff = f_rightNode.m_pos.x - a_targets[i].x;
				
				//check if it's the correct x position
				if(f_posDiff > -f_allowedDiff && f_posDiff < f_allowedDiff)
				{
					//calculate the difference of the y positions
					f_posDiff = f_rightNode.m_pos.y - a_targets[i].y;
					
					//check if it's the correct y position
					if(f_posDiff > -f_allowedDiff && f_posDiff < f_allowedDiff)
					{
						//we found it!
						f_foundNodes[i] = f_rightNode;
						
						f_foundCount++; //increase the counter
						
						//remove the target (to not search for it)
						a_targets[i] = null;
					}
				}
			}
			

			//move the right node along the edge
			f_rightNode = f_rightNode.m_neighbors[Node.GetRightSide(f_rightEdgeDir)];
			
			//move the left edge direction (since we moved the left node)
			//NOTE: move it the opposite direction to get the correct direction from the new node's perspective
			f_rightEdgeDir = Node.GetLeftSide(f_rightEdgeDir);
			
			//loop until we reached the next edge direction
			while(true)
			{
				//check if the next node is not null
				if(f_rightNode.m_neighbors[Node.GetRightSide(f_rightEdgeDir)] != null)
				{
					break;
				}
				
				//move the right edge direction
				f_rightEdgeDir = Node.GetRightSide(f_rightEdgeDir);
			}
			
			
			//check if we've gone in a full circle OR found all targets
			if(f_leftNode == f_rightNode || f_foundCount == a_targets.length)
			{
				break; //we've checked all edge nodes
			}
		}
		
		return f_foundNodes;
	}
	
	
	//returns the direction to an edge (next to a neighbor)
	public int GetEdgeDirection(Node a_startNode, int a_startDir, boolean a_clockwiseOrder)
	{
		boolean f_foundEdge = false;
		int f_dir = a_startDir;
		int f_edgeDir = 0;
		//calculate left edge direction
		while(true)
		{
			//check for edge
			if(a_startNode.m_neighbors[f_dir] == null)
			{
				//we found an edge
				f_foundEdge = true;
				
				//set the edge direction
				f_edgeDir = f_dir;
			}
			else
			{
				//if we left the edge
				if(f_foundEdge)
				{
					//return the edge direction
					return f_edgeDir;
				}
			}
			
			//next side
			if(a_clockwiseOrder)
			{
				//next right side
				f_dir = Node.GetRightSide(f_dir);
			}
			else
			{
				//next left side
				f_dir = Node.GetLeftSide(f_dir);
			}
		}
	}
	
	
	public Node[] GetNodeLine(Node a_startNode, int a_direction, int a_distance)
	{
		//initialization
		int i, n = 0;
		Node[] f_nodeLine = new Node[a_distance * 2 + 1];
		Node f_endNode = a_startNode;
		Node f_currentNode;
		int f_leftDir = Node.GetRightSide(Node.GetOppositeSide(a_direction));
		int f_rightDir = Node.GetLeftSide(Node.GetOppositeSide(a_direction));
		
		//get the end node
		for(i=0; i < a_distance; i++)
		{
			f_endNode = f_endNode.m_neighbors[a_direction];
		}
		//save end node
		f_nodeLine[n] = f_endNode;
		n++;
		
		
		//get the left line
		f_currentNode = f_endNode; //set to end node to start from there
		for(i=1; i <= a_distance; i++)
		{
			//get node
			f_currentNode = f_currentNode.m_neighbors[f_leftDir];
			
			//save node
			f_nodeLine[n] = f_currentNode;
			n++;
		}
		
		//get the right line
		f_currentNode = f_endNode; //set to end node to start from there
		for(i=1; i <= a_distance; i++)
		{
			//get node
			f_currentNode = f_currentNode.m_neighbors[f_rightDir];
			
			//save node
			f_nodeLine[n] = f_currentNode;
			n++;
		}
			
		
		return f_nodeLine;
	}
	
	
	public void IncreaseMap(Node a_startNode, int a_direction, int a_distanceToEdge)
	{
		//initialization
		int i, j, n;
		
		//get the nodes that will add the new nodes
		Node[] f_oldEdgeNodes = GetNodeLine(a_startNode, a_direction, a_distanceToEdge);
		List<Node> f_newNodes = new ArrayList<Node>();
		
		//add the new nodes
		for(i=0; i < f_oldEdgeNodes.length; i++)
		{	
			//if the neighbor don't exist
			if(f_oldEdgeNodes[i].m_neighbors[a_direction] == null)
			{
				//add the neighbor
				f_oldEdgeNodes[i].CreateNeighbor(Node.GetSide(a_direction));
				
				//add the new neighbor to the new node list
				f_newNodes.add(f_oldEdgeNodes[i].m_neighbors[a_direction]);
			}
			else
			{
				//check the 2 other sides of interest to know if it is an edge node or not
				if(f_oldEdgeNodes[i].m_neighbors[Node.GetLeftSide(a_direction)] == null
						|| f_oldEdgeNodes[i].m_neighbors[Node.GetRightSide(a_direction)] == null)
				{
					//it was an edge node after all!
				}
				else
				{
					//this is not an edge node, so we set it to null
					f_oldEdgeNodes[i] = null;
				}
			}
		}
		
		//check if there was any new
		if(f_newNodes.size() > 0)
		{
			//now we make a list of all missing neighbors of the new nodes (a wanted list)
			
			//list to connect the wanted list to the new nodes (and to know the side it belongs to)
			//NOTE: [x][0] = left, [x][1] = forward, [x][2] = right
			int[][] f_connectionList = new int[f_newNodes.size()][3];
			//compact list of vectors to search for
			List<Vector2> f_wantedList = new ArrayList<Vector2>();
			Vector2 f_pos = new Vector2();
			int f_dir = 0;
			boolean f_found;
			float f_posDiff;
			float f_allowedDiff = 0.001f;
			int f_notMissing = -1;
			int f_searchStartNodeIndex = -1;
			
			//loop through all new nodes
			for(i=0; i < f_newNodes.size(); i++)
			{
				//loop through the 3 sides of interest (left, forward, right)
				for(j=0; j < 3; j++)
				{
					//set the direction
					switch(j)
					{
					case 0:
						//check left
						f_dir = Node.GetLeftSide(a_direction);
						break;
					case 1:
						//check forward
						f_dir = a_direction;
						break;
					case 2:
						//check right
						f_dir = Node.GetRightSide(a_direction);
						break;
					}
					
					//if the side is missing a node
					if(f_newNodes.get(i).m_neighbors[f_dir] == null)
					{
						//get the position that the neighbor should have
						f_pos = f_newNodes.get(i).GetNeighborPos(f_dir);
						
						f_found = false;
						//loop through the wanted list to see if it's already there
						for(n=0; n < f_wantedList.size(); n++)
						{
							//calculate the difference of the x positions
							f_posDiff = f_pos.x - f_wantedList.get(n).x;
							
							//check if it's the correct x position
							if(f_posDiff > -f_allowedDiff && f_posDiff < f_allowedDiff)
							{
								//calculate the difference of the y positions
								f_posDiff = f_pos.y - f_wantedList.get(n).y;
								
								//check if it's the correct y position
								if(f_posDiff > -f_allowedDiff && f_posDiff < f_allowedDiff)
								{
									//we found it!
									//meaning: we don't need to add a new vector to the wanted list
									f_found = true;
									break;
								}
							}
						}
						
						//if the neighbor was found in the wanted list
						if(f_found)
						{
							//set the connection to the wanted's index
							f_connectionList[i][j] = n;
						}
						else
						{
							//set the connection to the wanted's (SOON TO BE) index
							f_connectionList[i][j] = f_wantedList.size();
							
							//add the neighbor's position to the wanted list
							f_wantedList.add(f_newNodes.get(i).GetNeighborPos(f_dir));
							
							//set the search start node to the node that is missing a node
							f_searchStartNodeIndex = i;
						}
					}
					else
					{
						//if it's not missing the node
						//NOTE: this is to know which we weren't looking for
						f_connectionList[i][j] = f_notMissing;
					}
				}
			}
			
			
			
		
			
			
			//check if there were any nodes to search for
			if(f_wantedList.size() > 0)
			{
				
				//create the search array
				Vector2[] f_searchArray = new Vector2[f_wantedList.size()];
				for(i=0; i < f_searchArray.length; i++)
				{
					//set the search array
					f_searchArray[i] = f_wantedList.get(i);
				}
				
				
				//search for the wanted nodes (using the search array)
				Node[] f_searchResults = FindEdgeNodes(f_searchArray, f_newNodes.get(f_searchStartNodeIndex), a_direction);
				
			
			
			
				//connect the found neighbors
				
				//loop through the connection array
				//NOTE: i = node index
				for(i=0; i < f_connectionList.length; i++)
				{
					//NOTE: n = side
					for(n=0; n < f_connectionList[i].length; n++)
					{
						//check if it's not missing
						if(f_connectionList[i][n] == f_notMissing)
						{
							continue;
						}
						
						//check if it was found
						if(f_searchResults[f_connectionList[i][n]] != null)
						{
							//set the direction
							switch(n)
							{
							case 0:
								//left
								f_dir = Node.GetLeftSide(a_direction);
								break;
							case 1:
								//forward
								f_dir = a_direction;
								break;
							case 2:
								//right
								f_dir = Node.GetRightSide(a_direction);
								break;
							}
							
							if(f_newNodes.get(i).m_neighbors[f_dir] != null)
							{
								System.out.println("SOMETHING IS TERRIBLY WRONG! (there is already a neighbor here!)");
							}
							
							//connect the new neighbors!
							
							//connect the new node to the other side's node
							f_newNodes.get(i).m_neighbors[f_dir] = f_searchResults[f_connectionList[i][n]];
							
							//connect the other side's node with the new node
							f_newNodes.get(i).m_neighbors[f_dir].m_neighbors[Node.GetOppositeSide(f_dir)] = f_newNodes.get(i);
						}
					}
				}
			}
		}
		
		
		//create an array list for the path leaders
		List<Node> f_oldLeaders = new ArrayList<Node>();
		int f_connectionCounter = 0;
		boolean f_isLeader;
		
		//pick out the path leader nodes
		for(i=0; i < f_oldEdgeNodes.length; i++)
		{
			//check if it's not null (if it was an edge node)
			if(f_oldEdgeNodes[i] != null)
			{
				//check if it's a path
				if(f_oldEdgeNodes[i].m_isPath)
				{
					
					//check if it is a leader
					f_isLeader = true; //true until proven otherwise
					f_connectionCounter = 0;
					for(j=0; j < f_oldEdgeNodes[i].m_neighbors.length; j++)
					{
						if(f_oldEdgeNodes[i].m_connectedTo[j])
						{
							f_connectionCounter++;
							
							if(f_connectionCounter > 1)
							{
								f_isLeader = false;
								break;
							}
						}
					}
					
					//if it was a leader
					if(f_isLeader)
					{
						//add it to the leaders list
						f_oldLeaders.add(f_oldEdgeNodes[i]);
					}
				}
			}
		}
		
		//if we found any old leaders
		if(f_oldLeaders.size() > 0)
		{
			//increase the leader nodes paths
			m_pathGenerator.IncreaseInvisiblePath();
		}
	}
	
	
	public void PerfectIncreaseMap(Node a_startNode, int a_direction)
	{
		Node f_currentNode = a_startNode.m_neighbors[a_direction];
		
		//reach out until we find the edge
		while(true)
		{
			//if we found an edge
			if(f_currentNode.m_neighbors[a_direction] == null)
			{
				break;
			}
			
			//next node
			f_currentNode = f_currentNode.m_neighbors[a_direction];
		}
		
		
		
		//go along the edges
		//initialize some variables
		int i;
		Node f_leftNode;
		Node f_rightNode;
		int f_leftEdgeDir = 0, f_rightEdgeDir = 0;
		
		boolean f_foundEdge = false;
		boolean f_foundNeighbor = false;
		
		//set the left edge node and that node's left edge direction
		
		//calculate the left edge direction of the start node
		f_leftEdgeDir = GetEdgeDirection(f_currentNode, a_direction, false);
		//set the left edge node
		f_leftNode = f_currentNode.m_neighbors[Node.GetLeftSide(f_leftEdgeDir)];
		//calculate the new left edge direction of the left edge node
		f_leftEdgeDir = GetEdgeDirection(f_leftNode, Node.GetRightSide(f_leftEdgeDir), false);
		
		
		
		//set the right edge node and that node's right edge direction
		
		//calculate the right edge direction of the start node
		f_rightEdgeDir = GetEdgeDirection(f_currentNode, a_direction, true);
		//set the right edge node
		f_rightNode = f_currentNode.m_neighbors[Node.GetRightSide(f_rightEdgeDir)];
		//calculate the new right edge direction of the right edge node
		f_rightEdgeDir = GetEdgeDirection(f_rightNode, Node.GetLeftSide(f_rightEdgeDir), true);
		
		
		//let's start increasing the map
		
		//create the neighbor of the middle node first
		f_currentNode.CreateNeighbor(a_direction);
		
		//loops until we've created all left side nodes
		while(f_leftNode.m_neighbors[a_direction] == null)
		{
			//create neighbor
			f_leftNode.CreateNeighbor(a_direction);
			
			//move the left node along the edge
			f_leftNode = f_leftNode.m_neighbors[Node.GetLeftSide(f_leftEdgeDir)];
			
			//move the left edge direction (since we moved the left node)
			//NOTE: move it the opposite direction to get the correct direction from the new node's perspective
			f_leftEdgeDir = Node.GetRightSide(f_leftEdgeDir);
			
			//loop until we reached the next edge direction
			while(true)
			{
				//check if the next node is not null
				if(f_leftNode.m_neighbors[Node.GetLeftSide(f_leftEdgeDir)] != null)
				{
					break;
				}
				
				//move the left edge direction
				f_leftEdgeDir = Node.GetLeftSide(f_leftEdgeDir);
			}
		}
		
		
		//loops until we've created all right side nodes
		while(f_rightNode.m_neighbors[a_direction] == null)
		{
			//create neighbor
			f_rightNode.CreateNeighbor(a_direction);

			//move the right node along the edge
			f_rightNode = f_rightNode.m_neighbors[Node.GetRightSide(f_rightEdgeDir)];
			
			//move the left edge direction (since we moved the left node)
			//NOTE: move it the opposite direction to get the correct direction from the new node's perspective
			f_rightEdgeDir = Node.GetLeftSide(f_rightEdgeDir);
			
			//loop until we reached the next edge direction
			while(true)
			{
				//check if the next node is not null
				if(f_rightNode.m_neighbors[Node.GetRightSide(f_rightEdgeDir)] != null)
				{
					break;
				}
				
				//move the right edge direction
				f_rightEdgeDir = Node.GetRightSide(f_rightEdgeDir);
			}
		}
		
		
		
		//now that we've created all new nodes
		//we will have to increase the paths (if any of the leaders no longer are at the edge)
		m_pathGenerator.IncreaseInvisiblePath();
	}
	
}
